#include <string>
#include <list>
#include <cstdlib>

#include "../config.h"
#include "proto_cddb.h"
#include "cfg.h"
#include "client.h"

// ---------------------------------------------
ProtoCddb::ProtoCddb()
// ---------------------------------------------
{
	name = "CDDB";
}

// ---------------------------------------------
ProtoCddb::~ProtoCddb()
// ---------------------------------------------
{
	if (connection.isOpen())
	{
		Response response = command("quit");
		app.getUI()->msg(Ui::DEBUG, "CDDB") << "quitting..." << Ui::END;
		app.getUI()->msg(Ui::DEBUG, "CDDB") << response.head << Ui::END;
		connection.close();
	}
}
// ---------------------------------------------
bool ProtoCddb::checkScheme(const std::string scheme)
// ---------------------------------------------
{	
	return (scheme.compare("cddb") == 0);
}

// ---------------------------------------------
bool ProtoCddb::fillDiscData(const uri_t uri, disc_t& info)
// ---------------------------------------------
{
	app.getUI()->msg(Ui::DEBUG, "CDDB") << "fillDiscData() called" << Ui::END;
	if (uri.scheme.compare("cddb") != 0)
	{
		return false;
	}
	
	if (!connect(uri))
	{
		app.getUI()->msg(Ui::NOTICE, "CDDB") << "Could not establish connection to the server." << Ui::END; 
		return false;
	}

	Response response;
	
	// prepare the command string
	std::string send = "cddb query " + info.cddb_id + " ";
	char tmpc[16];
	sprintf(tmpc, "%d ", info.tracks.size());
	send.append(tmpc);
	for (std::vector<track_t>::iterator i=info.tracks.begin(); i!=info.tracks.end(); i++)
	{
		sprintf(tmpc, "%d ", i->offset);
		send.append(tmpc);
	}
	sprintf(tmpc, "%d", info.length);
	send.append(tmpc);
	
	// query for the disc
	app.getUI()->msg(Ui::VERBOSE, "CDDB") << "quering for " << info.cddb_id << "..." << Ui::END;
	response = command(send);
	
	std::list<std::string> entries;
	std::list<std::string>::const_iterator ei;
	std::string category;

	// 2xx Query OK
	if (response.code[0] == '2')
	{
		app.getUI()->msg(Ui::DEBUG, "CDDB") << "2xx OK" << Ui::END;
		
		if (response.code[2] == '0')
		{
			// exact match(es)
			// first we get the IDs
						
			if (response.code[1] == '0')
			{
				// one match
				app.getUI()->msg(Ui::NOTICE, "CDDB") << "one exact match" << Ui::END;
				entries.push_back(response.head);
			}
			else if (response.code[1] == '1')
			{
				// more than one match
				app.getUI()->msg(Ui::NOTICE, "CDDB") << "multiple exact matches" << Ui::END;
				size_t tmp = 0;
				size_t tmp2 = 0;
				while (1)
				{
					// line end
					tmp2 = response.body.find('\n', tmp+1);
					// extract line
					send = response.body.substr(tmp, tmp2-tmp);
					// next line start
					tmp = tmp2+1;
					if (send[0] == '.') break;
					
					app.getUI()->msg(Ui::DEBUG, "CDDB") << send << Ui::END;
					
					entries.push_back(send);
				}
			}

			// Then we request each entry
			std::list<disc_t> disclist;
			std::string path;
			app.getUI()->msg(Ui::DEBUG, "CDDB") << "Get entries" << Ui::END;
			
			// iterate IDs and request each
			for (ei=entries.begin(); ei!=entries.end(); ++ei)
			{
				app.getUI()->msg(Ui::DEBUG, "CDDB") << "Quering for " << *ei << Ui::END;
				// query
				send = "cddb read ";
				size_t tmp = ei->find(' ');
				category = ei->substr(0, tmp);
				send.append(ei->substr(0, ei->find(' ', tmp+1)));
				response = command(send);				
				// push back
				disclist.push_back( readCddbEntry(response.body, info) );
			}

			// ask the user to choose the entry
			std::list<disc_t>::const_iterator chosen;
			if (entries.size() > 1)
			{
				chosen = app.getUI()->chooseDisc(disclist);
			}
			else
			{
				chosen = disclist.begin();
			}
			info = *chosen;
		}
		
		// 2x1 Inexact matches
		else if (response.code[2] == '1')
		{
			// Specification is such a junk we don't know
			// what the hell are inexact matches
			app.getUI()->msg(Ui::NOTICE, "CDDB") << "Inexact matches not supported yet" << Ui::END;
			app.getUI()->msg(Ui::DEBUG, "CDDB") << "Send the following to the authors:" << Ui::END;
			app.getUI()->msg(Ui::DEBUG, "CDDB") << "INEXACT: " << response.head << Ui::END;
			app.getUI()->msg(Ui::DEBUG, "CDDB") << response.body << Ui::END;
			return false;
		}
		// 2x2 no matches
		else if (response.code[2] == '2')
		{
			app.getUI()->msg(Ui::VERBOSE, "CDDB") << "No matches" << Ui::END;
			return false;
		}
	}
	
	// 4xx Error
	else if (response.code[0] == '4')
	{
		app.getUI()->msg(Ui::VERBOSE, "CDDB") << "4xx error" << Ui::END;
		if (response.code[2] == '3')
		{
			// Corrupt entry
			app.getUI()->msg(Ui::VERBOSE, "CDDB") << "Entry corrupt" << Ui::END;
		}
		else if (response.code[2] == '9')
		{
			// No handshake
			app.getUI()->msg(Ui::VERBOSE, "CDDB") << "No handshake" << Ui::END;
		}
		return false;
	}
	return true;
}


// ---------------------------------------------
bool ProtoCddb::connect(const uri_t uri)
// ---------------------------------------------
{
	if (connection.isOpen()) return true;

	app.getUI()->msg(Ui::DEBUG, "CDDB") << "connect() called" << Ui::END;
	std::string recv;
	
	// try to connect
	if (!connection.establish(uri.host, uri.port, &recv))
	{
		return false;
	}
	
	// parse response
	if (recv.substr(0, 1) == "4") 
	{
		// 4xx, connection refused
		app.getUI()->msg(Ui::VERBOSE, "CDDB") << recv << Ui::END;
		return false;
	}
	
	Response response;
	response = command("cddb hello user host " PACKAGE_STRING);
	
	if (response.code[0] == '4')
	{
		app.getUI()->msg(Ui::VERBOSE, "CDDB") << "Handshake failed: " << response.head << Ui::END;
		return false;
	}
	app.getUI()->msg(Ui::DEBUG, "CDDB") << response.head << Ui::END;

	response = command("proto 6");
	if (response.code == "501")
	{
		app.getUI()->msg(Ui::VERBOSE, "CDDB") << "Protocol Level 6 not supported: " << response.head << Ui::END;
		return false;
	}
	
	return true;
}

// ---------------------------------------------
ProtoCddb::Response ProtoCddb::command(const char* text)
// ---------------------------------------------
{
	std::string s;
	s.assign(text);
	return command(s);
}

// ---------------------------------------------
ProtoCddb::Response ProtoCddb::command(std::string command)
// ---------------------------------------------
{
	std::string recv;
	connection.sendrecv(command.append("\n").data(), &recv);
	Response response = parse(recv);
	if (response.code[1] == '1')
	{
		// x1x, data till .
		while (response.body.find("\n.") == std::string::npos)
		{
			connection.sendrecv(" \n", &recv);
			response = parse(recv);
		}
	}

	app.getUI()->msg(Ui::DEBUG, "CDDB") << "command():sent: " << command.substr(0, -1) << Ui::END;
	app.getUI()->msg(Ui::DEBUG, "CDDB") << "command():got: " << response.code << " with head: " << response.head << Ui::END;
	app.getUI()->msg(Ui::DEBUG, "CDDB") << "command():body:\n" << response.body << Ui::END;

	return response;
}

// ---------------------------------------------
ProtoCddb::Response ProtoCddb::parse(std::string response)
// ---------------------------------------------
{
	Response output;
	output.code = response.substr(0, 3);
	output.head = response.substr(4, response.find("\n")-5);
	output.body = response.substr(response.find("\n")+1);
	return output;
}

// ---------------------------------------------
disc_t ProtoCddb::readCddbEntry(std::string& data, const disc_t& info)
// ---------------------------------------------
{
	app.getUI()->msg(Ui::DEBUG, "CDDB") << "readCddbEntry()" << Ui::END;
	disc_t parsed = info;

	size_t pos;
	while (pos = data.find("\n"), pos != std::string::npos)
	{		
		std::string line(data.substr(0, pos-1));
		data = data.substr(pos+1);
		app.getUI()->msg(Ui::DEBUG, "CDDB") << line << Ui::END;

		if (line[0] == '#') continue;
		if (line.compare(0, 7, "DISCID=") == 0)
		{
			parsed.cddb_id = line.substr(7);
		}
		else
		if (line.compare(0, 6, "DYEAR=") == 0)
		{
			parsed.year = line.substr(6);
		}
		else
		if (line.compare(0, 7, "DGENRE=") == 0)
		{
			parsed.genre = line.substr(7);
		}
		else
		if (line.compare(0, 7, "DTITLE=") == 0)
		{
			line = line.substr(7);
			pos = line.find(" / ");
			if (pos == std::string::npos)
			{
				parsed.title = line;
			}
			else
			{
				parsed.artist.assign(line, 0, pos);
				parsed.title = line.substr(pos+3);
			}
		}
		else
		if (line.compare(0, 5, "EXTD=") == 0)
		{
			parsed.comment = line.substr(5);
		}
		else
		if (line.compare(0, 6, "TTITLE") == 0)
		{
			line = line.substr(6);
			pos = line.find("=");
			if (pos == std::string::npos) continue;
			int i = atoi(line.substr(0, pos).data());
			line = line.substr(pos+1);
			pos = line.find(" / ");
			if (pos == std::string::npos)
			{
				parsed.tracks[i].artist = parsed.artist;
				parsed.tracks[i].title = line;
			}
			else
			{
				parsed.tracks[i].artist.assign(line, 0, pos);
				parsed.tracks[i].title = line.substr(pos+3);
			}
		}
		else
		if (line.compare(0, 4, "EXTT") == 0)
		{
			line = line.substr(4);
			pos = line.find("=");
			if (pos == std::string::npos) continue;
			int i = atoi(line.substr(0, pos).data());
			parsed.tracks[i].comment = line.substr(pos+1);
		}
	}
	return parsed;
}

